﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Text;

namespace Central.Linq {

	/// <summary>
	/// Определение группировки для запроса
	/// </summary>
	public class GroupManager {
		#region конструктор

		/// <summary>
		/// Создает экземпляр группировки
		/// </summary>
		public GroupManager() {
			this.groupFields = new Collection<string>();
			this.aggregateFields = new Collection<AggregateFieldDefinition>();
		}

		/// <summary>
		/// Создает экземпляр группировки c предустановленным группировочными полями
		/// </summary>
		public GroupManager(Collection<string> groupFields)
			: this() {
			this.groupFields = groupFields;
		}

		/// <summary>
		/// Создает экземпляр группировки c предустановленным группировочными и агрегирующими полями
		/// </summary>
		public GroupManager(Collection<string> groupFields, Collection<AggregateFieldDefinition> aggregateFields)
			: this(groupFields) {
			this.aggregateFields = aggregateFields;
		}

		/// <summary>
		/// Создает экземпляр группировки c предустановленным группировочными и агрегирующими полями
		/// </summary>
		public GroupManager(Collection<string> groupFields, Collection<AggregateFieldDefinition> aggregateFields, string fieldNameCount)
			: this(groupFields, aggregateFields) {
			this.FieldNameForCount = fieldNameCount;
		}

		#endregion конструктор

		private Collection<string> groupFields;

		/// <summary>
		/// Коллекция полей сущности, входящих в группировку
		/// </summary>
		public IEnumerable<string> GroupFields { get { return groupFields; } }

		private Collection<AggregateFieldDefinition> aggregateFields;

		/// <summary>
		/// Коллекция агрегирующих полей сущности
		/// </summary>
		public IEnumerable<AggregateFieldDefinition> AggregateFields { get { return aggregateFields; } }

		/// <summary>
		/// Название поля типа int, куда будет возвращаться агрегирующая функция - количество
		/// </summary>
		public string FieldNameForCount { get; set; }

		#region [+] cвойства

		/// <summary>
		/// Возвращаемый предикат группировки
		/// </summary>
		public GroupPredicate Predicate {
			get {
				return this.GeneratePredicate();
			}
		}

		/// <summary>
		/// Возвращаемое количество группировочных полей
		/// </summary>
		public int Count {
			get {
				return this.groupFields == null ? 0 : this.groupFields.Count;
			}
		}

		#endregion [+] cвойства

		#region методы

		/// <summary>
		/// Метод добавления поля для группировки
		/// </summary>
		/// <param name="groupField">поле группировки</param>
		public void Add(string groupField) {
			if (String.IsNullOrEmpty(groupField))
				return;
			if (this.groupFields == null)
				this.groupFields = new Collection<string>();

			if (this.groupFields.Contains(groupField))
				this.groupFields.Remove(groupField);

			this.groupFields.Add(groupField);
		}

		/// <summary>
		/// Метод вставки поля для группировки в заданное место
		/// </summary>
		/// <param name="index">место вставки</param>
		/// <param name="groupField">поле группировки</param>
		public void Insert(int index, string groupField) {
			if (String.IsNullOrEmpty(groupField))
				return;
			if (this.groupFields == null)
				this.groupFields = new Collection<string>();

			if (this.groupFields.Contains(groupField))
				this.groupFields.Remove(groupField);

			this.groupFields.Insert(index, groupField);
		}

		/// <summary>
		/// Метод удаления поля для группировки из коллекции
		/// </summary>
		/// <param name="groupField">поле группировки</param>
		public void Remove(string groupField) {
			if (String.IsNullOrEmpty(groupField))
				return;

			if (this.groupFields != null)
				this.groupFields.Remove(groupField);
		}

		/// <summary>
		/// Метод очищения коллекции
		/// </summary>
		public void Clear() {
			if (this.groupFields != null)
				this.groupFields.Clear();
		}

		/// <summary>
		/// Метод добавления агрегирующего поля в коллекцию
		/// </summary>
		/// <param name="aggregateField">агрегирующее поле</param>
		public void AddAggregateField(AggregateFieldDefinition aggregateField) {
			if (aggregateField == null)
				return;
			if (this.aggregateFields == null)
				this.aggregateFields = new Collection<AggregateFieldDefinition>();

			if (!this.aggregateFields.Contains(aggregateField))
				this.aggregateFields.Add(aggregateField);
		}

		/// <summary>
		/// Метод удаления агрегирующего поля из коллекции
		/// </summary>
		/// <param name="aggregateField">агрегирующее поле</param>
		public void RemoveAggregateField(AggregateFieldDefinition aggregateField) {
			if (aggregateField == null)
				return;

			if (this.aggregateFields != null)
				this.aggregateFields.Remove(aggregateField);
		}

		/// <summary>
		/// Метод очищения коллекции агрегирующих полей
		/// </summary>
		public void ClearAggregateFields() {
			if (this.aggregateFields != null)
				this.aggregateFields.Clear();
		}

		#endregion методы

		#region скрытые методы

		private GroupPredicate GeneratePredicate() {
			GroupPredicate predicate = new GroupPredicate();
			StringBuilder keyFieldsPredicate = new StringBuilder();
			if (this.Count > 0) {
				lock (this.GroupFields) {
					StringBuilder groupPredicate = new StringBuilder();
					foreach (string item in this.GroupFields) {
						if (groupPredicate.Length == 0) {
							groupPredicate.Append(item);
							keyFieldsPredicate.Append(string.Format(CultureInfo.CurrentCulture, "Key.{0} AS {0}", item));
						}
						else {
							groupPredicate.Append(String.Concat(", ", item));
							keyFieldsPredicate.Append(string.Format(CultureInfo.CurrentCulture, ", Key.{0} AS {0}", item));
						}
					}
					predicate.Group = string.Format(CultureInfo.CurrentCulture, "new ({0})", groupPredicate.ToString());
				}
				if (this.AggregateFields != null) {
					lock (this.AggregateFields) {
						StringBuilder aggregateFieldsPredicate = new StringBuilder();
						foreach (AggregateFieldDefinition item in this.AggregateFields) {
							if (!this.GroupFields.Contains(item.Field)) {
								aggregateFieldsPredicate.Append(String.Concat(", ", System.String.Format(CultureInfo.CurrentCulture, item.Function.GeneratePredicate(), item.Field), " AS ", item.Field));
							}
						}
						if (string.IsNullOrEmpty(this.FieldNameForCount))
							predicate.Select = string.Format(CultureInfo.CurrentCulture, "new ({0}{1})", keyFieldsPredicate.ToString(), aggregateFieldsPredicate.ToString());
						else
							predicate.Select = string.Format("new ({0}{1}, COUNT() AS {2})", keyFieldsPredicate.ToString(), aggregateFieldsPredicate.ToString(), this.FieldNameForCount);
					}
				}
				else {
					if (string.IsNullOrEmpty(this.FieldNameForCount))
						predicate.Select = string.Format(CultureInfo.CurrentCulture, "new ({0})", keyFieldsPredicate.ToString());
					else
						predicate.Select = string.Format("new ({0}, COUNT() AS {1})", keyFieldsPredicate.ToString(), this.FieldNameForCount);
				}
			}
			return predicate;
		}

		#endregion скрытые методы
	}
}